Declaraciones del Bison
Como se mencionó anteriormente, en esta parte se
declaran los símbolos que se utilizan en gramática y además se definen los
tipos de los valores
semánticos.
Los no terminales se declaran si es necesario asociarle
un valor
semántico.
Ejemplo de declaración de Tokens:
%token NUM /* Se declara un token que tiene asociado el
tipo de datos por
defecto*/
Para crear varios tipos se utiliza
%union {
int itype; /* Definimos un tipo entero que se llama
itype*/
double dtype; /* Definimos un tipo double que se llama
dtype*/
}
Con esta especificación de tipos cuando
declaramos un Token tenemos que especificarle el tipo que tiene
asociado.
%token <itype> TK_INT /* Se declara un Token
literal entero*/
%token <dtype> TK_DOUBLE /* Se declara un token
literal real*/
Precedencia de
los operadores
Se utiliza %left, %right o %nonassoc para especificar la
asociatividad de los operadores, la prioridad se establece por el
orden de aparición en la declaración.
Para declarar un Token de esta forma se trabaja con la
misma sintaxis que para declarar un Token de la forma %token
visto anteriormente.
Ejemplo:
%right '='
%left '-' '+'
%left '*' '/'
%left NEG /* Negación, menos unario */
%right '^' /* Potencia
*/
En este ejemplo definimos que el de menor precedencia es
el operador de asignación y se define que es asociativo
por la derecha, le siguen los operadores de adición y
substracción que tienen la misma precedencia entre ellos,
mayor que la asignación y son asociativos por la
izquierda, así sucesivamente, y por último se
define que el de mayor precedencia es el operador de
potenciación.
Declaración
de no terminales
La misma sintaxis que con %token pero utilizando
%type
Ejemplo:
%type <itype> exp
Donde itype se tiene que haber creado en %union como
vimos anteriormente.
Resumen de las declaraciones del Bison
%skeleton "lalr1.cc": Esta directiva es necesaria
para compilar el proyecto para
C++, la función es
crear los ficheros:
- 'position.hh'
- 'location.hh' (Estos dos primeros ficheros son
utilizados para la localización y posición de los
tokens en el fichero fuente) - 'stack.hh' (Pila auxiliar utilizada por el
parser) - 'file.hh'
- 'file.cc' (Declaración e implementación
de la clase
parser, 'file' es el nombre del fichero de salida)
%define "parser_class_name" "name": Cambia el
nombre por defecto de los ficheros 'output.hh' y 'output.cc' por
el definido en "name".
%defines: Se utiliza para especificarle al Bison
que cree el fichero "file.hh", por defecto solo se crea
"file.cc".
%error-verbose: esta directiva se utiliza para
habilitar la muestra de
mensajes de error (%debug habilita el traceo durante el proceso de
parsing)
%parse-param{argument_declaration}: Declara un
parámetro adicional que yylex acepta.
%lex-param {argument_declaration}: Declara un
parámetro adicional que yylex acepta.
%locations: Cuando es usada, el parser habilita
la búsqueda de localizaciones, dos clases auxiliares
definen una posición (un puntero a un archivo) y una
localización (un rango compuesto por un par de
posiciones).
%initial-action: Corre el código
del usuario antes de parsear.
@$: es el conjunto para el rango desde @1 hasta
@n, de una regla con n componentes.
%destructor { delete $$; } "identificador":
Habilita la liberación de memoria durante
el proceso de recuperación de errores.
Introducción
al Flex:
El Flex define las reglas de reconocimiento de
símbolos (Tokens) a partir de expresiones
regulares. Cuando un Token es reconocido por uno de estos
patrones de agrupamiento se le define una acción,
por lo general esta acción es devolver el Tipo y el valor
(lexema).
El Flex cuando se utiliza combinado con el Bison,
utiliza las definiciones de los Tokens realizadas en el Bison
para la
comunicación entre ellos,
Los ficheros del Flex para C++ utilizan por convenio la
extensión ‘.ll’. La sintaxis de un fichero en
flex es la siguiente:
… definiciones …
%%
… reglas…
%%
… subrutinas …
Expresiones Regulares
Caracteres Especiales | Explicación |
. | Reconoce todos los caracteres excepto los cambios |
n | Cambio de línea |
* | Repite cero o muchas veces |
+ | Repite una o muchas veces |
? | Repite cero o una vez |
^ | Define el inicio de la expresión |
$ | Define el final de la expresión |
| | Operador OR |
"" | Define una cadena, dentro se tienen que escapar |
[] | Agrupa un rango de caracteres de los cuales se |
() | Agrupa expresiones regulares. |
{} | Expande los tokens o reconoce determinada cadena |
Ejemplos:
abc abc
abc* ab abc abcc abccc …
abc+ abc abcc abccc …
a(bc)+ abc abcbc abcbcbc …
a(bc)? a abc
[abc] Uno entre: a, b, c
[a-z] Una letra en el rango de la a-z
[a-z] Uno entre: a, -, z (Escaparon el menos porque
tiene un significado diferente (El de establecer el rango
cuando esta entre dos valores del
mismo tipo))
[-az] Uno entre: -, a, z
[A-Za-z0-9]+ Al menos un carácter alfanumérico.
[ tn]+ Espacios en blanco.
[^ab] Cualquier carácter excepto: a, b (Porque
^ está al inicio)
[a^b] Uno entre: a, ^, b
[a|b] Uno entre: a, |, b
a|b Uno entre: a, b
{letra}+ si se definió el Token letra, entonces
a partir de esta nueva definición se pueden definir
palabras
a{1,5} reconoce las cadenas donde se repita la 'a' de
1 a 5 veces
Partes del Fichero
Se había mostrado las partes de un fichero en
Flex:
… definiciones …
%%
… reglas…
%%
… subrutinas …
Las definiciones y las subrutinas no son obligatorias,
en las reglas siempre hay que definir al menos una, y siempre
después de ‘%%’ aunque no existan definiciones
y si no existen subrutinas no es necesario poner el último
‘%%’.
Definiciones
En esta parte si una línea comienza con un
espacio en blanco o con un tabulador el contenido de esa
línea se copia exactamente en el fichero de código
C++ que se genera.
Las directivas que se vayan a utilizar en el programa se
colocan entre ‘%{‘ y ‘}%’
Todo lo que se coloque en la primera columna de esta
sección y que no este entre ‘%{‘ y
‘}%’ se tomará como una regla de
sustitución de cadenas de la forma:
nombre patrón
Esto se utiliza para definir los Tokens que se desean
reconocer, por ejemplo:
digito [1-9]
letra [a-zA-Z]
numero {digito}+
palabra {letra}+
blanco [ t]+
rep a{1,5}
Reglas
Las reglas están formadas por un reconocimiento
de un Token y una acción asociada a este reconocimiento,
si no se define ninguna acción no se hace nada cuando se
reconoce dicho Token.
Si aparecen cadenas que no se reconocen por ninguno de
los tokens estas se imprimen como aparecen. Una solución
para que esto no pase es poner una regla que sea '.' que reconoce
cualquier carácter excepto un cambio de
línea, pudiéndole asociar otro comportamiento
como devolver un Token de error léxico, etc.
Las reglas se pueden realizar a partir de lo que se
definió anteriormente, un ejemplo de esto:
{blanco} ;
Esta regla define que no se haga nada al aparecer un
espacio en blanco o un tabulador
{rep} printf("%d", yyleng);
Esta regla define que cada vez que se encuentre una
cadena compuesta por 'a' entre 1 y 5 repeticiones, imprima la
longitud de dicha cadena.
{palabra} printf("%s",yytext);
Cada vez que se encuentre alguna palabra, esta se
imprime en pantalla, si entre dos palabras hay un espacio en
blanco, este no se imprime (por la primera regla)
{numero} printf("n");
Cada vez que se encuentre algún número
imprime en pantalla un cambio de línea.
Cuando las reglas son complejas podemos definirlas entre
llaves y escribirlas en varias líneas.
Subrutinas
Se implementan funciones que
serán copiadas para el fichero con el código en C++
exactamente como se implementan en esta
sección.
Datos de los autores:
Daynel Marmol Lacal
Lic. en Ciencias de la
Computación
Profesor de programación
Universidad de las Ciencias Informáticas
(UCI)
Cuidad de La Habana, Cuba
Leansy Alfonso Pérez
Ing. Informático
Profesor de Programación
Universidad de las Ciencias Informáticas
(UCI)
Cuidad de La Habana, Cuba
Categoría del trabajo:
Programación
Página anterior | Volver al principio del trabajo | Página siguiente |